#include <typedef.h>
#include <multiboot.h>
#include <utils.h>
#include <mem.h>
struct memory_map memorymap[256];
u32 mapindex = 0;

extern u8 KERNEL_BEGINS;
extern u8 KERNEL_ENDS;

char *lookup_memtype(u32 type)
{
  switch(type)
  {
    case INVAILD_TYPE:
      return "Invaild";
    case FREE_TYPE:
      return "Free";
    case RESV_TYPE:
      return "Reserved";
    case APCI_RECL_TYPE:
      return "ACPI reclaimable";
    case ACPI_NVS_TYPE:
      return "ACPI NVS";
    case BADMEM_TYPE:
      return "BAD";
    case KERNEL_TYPE:
      return "Kernel";
    case MULTIBOOT_TYPE:
      return "Multiboot";
    case MODULE_TYPE:
      return "Module";
    case ELF_TYPE:
      return "Elf";
    default:
      return "Unknown";
  }
}
void mem_used(u32 base, u32 length, u32 type)
{
  memorymap[mapindex].base_addr_low = base;
  memorymap[mapindex].length_low = length;
  memorymap[mapindex].type = type;
  mapindex++;
}

void mem_print_map()
{
  u32 i;
  kprintf("Memory map:\n");

  for(i = 0; i < mapindex; i++)
  {
    kprintf("  Base: 0x%x Length: 0x%x Type: %s\n",
      memorymap[i].base_addr_low, memorymap[i].length_low, lookup_memtype(memorymap[i].type));
  }
}
void mem_init(struct multiboot* mb)
{  
  struct memory_map* memmap;
  struct module* modules;
  u32 i;
  memmap = (void*) mb->mmap_addr;
  while((u32)memmap < (mb->mmap_addr + mb->mmap_length))
  {
    mem_used(memmap->base_addr_low, memmap->length_low, memmap->type);
    memmap = (void*) ((u32)memmap + memmap->size + sizeof(memmap->size)); 
  }
  
  kprintf("0x%x - 0x%x (0x%x)   Kernel\n",
    &KERNEL_BEGINS, &KERNEL_ENDS, &KERNEL_ENDS - &KERNEL_BEGINS);    
  mem_used(&KERNEL_BEGINS, &KERNEL_ENDS - &KERNEL_BEGINS, KERNEL_TYPE);
  
  kprintf("0x%x - 0x%x (0x%x)   Multiboot\n", mb, mb + sizeof(struct multiboot), sizeof(struct multiboot));
  mem_used((u32) mb, sizeof(struct multiboot), MULTIBOOT_TYPE);
  
  modules = (void*) mb->mods_addr;
  for(i = 0; i < mb->mods_count;i++)
  {
    kprintf("0x%x - 0x%x (0x%x)   Module: %s\n",
      modules[i].mod_start, modules[i].mod_end, modules[i].mod_end - modules[i].mod_start, modules[i].name);
    mem_used(modules[i].mod_start, modules[i].mod_end - modules[i].mod_start, MODULE_TYPE);    
  }
}
void mem_bubblesort()
{ /* sort the map, using bubblesort*/
  bool change = true;
  u32 temp;
  u32 i;
  while(change)
  {
    change = false;
    for(i = 0; i < mapindex-1; i++)  
    {
      if(memorymap[i].base_addr_low > memorymap[i+1].base_addr_low)
      {
        temp = memorymap[i].base_addr_low;
        memorymap[i].base_addr_low = memorymap[i+1].base_addr_low;
        memorymap[i+1].base_addr_low = temp;
     
        temp = memorymap[i].length_low;
        memorymap[i].length_low = memorymap[i+1].length_low;
        memorymap[i+1].length_low = temp;
      
        temp = memorymap[i].type;
        memorymap[i].type = memorymap[i+1].type;
        memorymap[i+1].type = temp;
        change = true;
      }
    }
  }
}
void mem_overlapping()
{
  u32 before_base, after_base, before_length, after_length;
  u32 i;
  bool change = true;
  /* check and fix for overlapings */
  while(change)
  {
    change = false;
    for(i = 0; i < mapindex-1; i++)
    {
      if(memorymap[i].base_addr_low + memorymap[i].length_low > memorymap[i+1].base_addr_low)
      {
        if(memorymap[i].type > memorymap[i+1].type)
        { 
          memorymap[i+1].length_low -= memorymap[i+1].base_addr_low - memorymap[i].base_addr_low;
          memorymap[i+1].base_addr_low = memorymap[i].base_addr_low + memorymap[i].length_low;
        }
        if(memorymap[i].type < memorymap[i+1].type)
        {     
          before_base = memorymap[i].base_addr_low;
          after_base = memorymap[i+1].base_addr_low + memorymap[i+1].length_low; 
        
          before_length = memorymap[i+1].base_addr_low - memorymap[i].base_addr_low; 
          if(after_base <= memorymap[i].base_addr_low + memorymap[i].length_low)
            after_length = memorymap[i].base_addr_low + memorymap[i].length_low - after_base;
          else
            after_length = 0;
        
          mem_used(before_base, before_length, memorymap[i].type);
          mem_used(after_base, after_length, memorymap[i].type);
        
          memorymap[i].length_low = 0;
        }
        mem_bubblesort();
        change = true;
      }  
    }
  }
}
void mem_remove0length()
{
  u32 removenum;
  u32 i;
  /* check for 0-length mappings, count them and make them invaild */
  removenum = 0;    
  for(i = 0; i < mapindex; i++)
    if(memorymap[i].length_low == 0)
    {
      memorymap[i].base_addr_low = 0xffffffff;
      memorymap[i].type = INVAILD_TYPE;
      removenum++;
    }
    
  mem_bubblesort();
  mapindex -= removenum;
}
void mem_sort()
{
  mem_bubblesort();
   
  mem_overlapping();
  mem_remove0length();
}

void mem_done()
{
  mem_print_map();
  mem_sort();
  mem_sort();
  mem_print_map();
}
